/*
{{className}}.java
Copyright (C) 2010  Belledonne Communications, Grenoble, France

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package {{packageName}};

{{#imports}}
import {{import}}
{{/imports}}
{{#isLinphoneFactory}}
import android.content.Context;
import android.os.Build;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.linphone.mediastream.Log;
import org.linphone.mediastream.Version;
import org.linphone.core.tools.OpenH264DownloadHelper;
{{/isLinphoneFactory}}

{{#doc}}
/**
    {{#lines}}
  * {{line}}
    {{/lines}}
  */
{{/doc}}
public {{#isLinphoneFactory}}abstract class{{/isLinphoneFactory}}{{#isNotLinphoneFactory}}interface{{/isNotLinphoneFactory}} {{className}} {
{{#enums}}
    public enum {{{className}}} {
    {{#values}}
        {{#doc}}
        /**
            {{#lines}}
        * {{line}}
            {{/lines}}
        */
        {{/doc}}
        {{name}}({{value}}){{commarorsemicolon}}

    {{/values}}
        protected final int mValue;

        private {{{className}}} (int value) {
            mValue = value;
        }

        static public {{{className}}} fromInt(int value) throws RuntimeException {
            switch(value) {
            {{#values}}
            case {{value}}: return {{name}};
            {{/values}}
            default:
                throw new RuntimeException("Unhandled enum value " + value + " for {{{className}}}");
            }
        }

        public int toInt() {
            return mValue;
        }
    };

{{/enums}}
{{#isLinphoneFactory}}
    static Factory _Factory;

    public static final synchronized Factory instance() {
		try {
			if (_Factory == null) {
				_Factory = new FactoryImpl(0); // This value is not relevant, correct factory pointer will be used in JNI layer
			}
		} catch (Exception e) {
			System.err.println("Cannot instanciate factory");
		}
		return _Factory;
	}

    abstract public OpenH264DownloadHelper createOpenH264DownloadHelper(Context context);

    /**
      * Gets the LoggingService singleton
      */
    abstract public LoggingService getLoggingService();

    abstract public void setDebugMode(boolean enable, String tag);

    abstract public Core getCore(long ptr);

{{/isLinphoneFactory}}
{{#isLinphoneCore}}
    /**
      * Gets the mediastreamer's factory
      */
    public org.linphone.mediastream.Factory getMediastreamerFactory();
{{/isLinphoneCore}}
{{#methods}}
    {{#doc}}
    /**
    {{#lines}}
      * {{line}}
    {{/lines}}
      */
    {{/doc}}
    {{#deprecated}}@Deprecated
    {{/deprecated}}{{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public {{return}} {{name}}({{params}});

{{/methods}}
    /**
      * Sets the object to store in this object user's data
      */
    {{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public void setUserData(Object data);

    /**
      * Gets the object stored in this object user's data
      */
    {{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public Object getUserData();
}

class {{classImplName}} {{#isLinphoneFactory}}extends{{/isLinphoneFactory}}{{#isNotLinphoneFactory}}implements{{/isNotLinphoneFactory}} {{className}} {

    protected long nativePtr = 0;
    protected Object userData = null;{{#hasCoreAccessor}}
    protected Core core = null;{{/hasCoreAccessor}}

    protected {{classImplName}}(long ptr) {
        nativePtr = ptr;{{#hasCoreAccessor}}
        core = getCore();{{/hasCoreAccessor}}
    }

{{#isLinphoneFactory}}
	private static boolean loadOptionalLibrary(String s) {
		try {
			System.loadLibrary(s);
			return true;
		} catch (Throwable e) {
			android.util.Log.w("FactoryImpl", "Unable to load optional library " + s + ": " + e.getMessage());
		}
		return false;
	}

	static {
		System.loadLibrary("c++_shared");
		loadOptionalLibrary("ffmpeg-linphone");
		System.loadLibrary("bctoolbox");
		System.loadLibrary("ortp");
		System.loadLibrary("mediastreamer");
		System.loadLibrary("linphone");
		Version.dumpCapabilities();
	}

    public OpenH264DownloadHelper createOpenH264DownloadHelper(Context context) {
		if (context == null) {
			new CoreException("Cannot create OpenH264DownloadHelper");
			return null;
		}
		return new OpenH264DownloadHelper(context);
	}

	private native Core getCore(long nativePtr, long ptr);
	@Override
	public Core getCore(long ptr) {
	    return getCore(nativePtr, ptr);
	}

    @Override
	public LoggingService getLoggingService() {
		LoggingService l = new LoggingServiceImpl(0);
		return l.get();
	}

    @Override
    public native void setDebugMode(boolean enable, String tag);
{{/isLinphoneFactory}}

{{#methods}}
    private native {{return_native}} {{name_native}}({{native_params}});
    @Override
    synchronized public {{return}} {{name}}({{params}}) {{#exception}}throws CoreException{{/exception}} {
        {{#hasCoreAccessor}}{{#isNotGetCore}}synchronized(core) { {{/isNotGetCore}}{{/hasCoreAccessor}}
        {{#exception}}int exceptionResult = {{/exception}}{{return_keyword}}{{#enumCast}}{{return}}.fromInt({{/enumCast}}{{#classCast}}({{return}}){{/classCast}}{{name_native}}({{native_params_impl}}){{#enumCast}}){{/enumCast}};{{#exception}}
        if (exceptionResult != 0) throw new CoreException("{{name}} returned value " + exceptionResult);{{/exception}}{{#hasCoreAccessor}}{{#isNotGetCore}}
        }{{/isNotGetCore}}{{/hasCoreAccessor}}
    }

{{/methods}}
{{#isLinphoneCore}}
    private native org.linphone.mediastream.Factory getMediastreamerFactory(long nativePtr);
    public org.linphone.mediastream.Factory getMediastreamerFactory() {
        return getMediastreamerFactory(nativePtr);
    }

{{/isLinphoneCore}}
{{#isNotLinphoneFactory}}
    private native boolean unref(long ptr);
    protected void finalize() throws Throwable {
        /* Considering the following scenario:
        User scrolls fast in a chat message list that contains a lot of images, 
        so the VM will garbage collect often due to the images taking much space.
        The view itself only has a reference on the EventLogs, not the ChatMessages directly,
        so each call of onBindView will call eventLog.getChatMessage() and the reference will only be temporary.
        It has been observed a race condition can occurs in which the JNI level can obtain a LocalRef from a WeakGlobalRef
        of an object that has been scheduled for destruction and for which the finalize() method will be called shortly after.
        The result is a Java object that has been finalized can have it's methods be called after the finalize() has terminated.*/
        
        /*The workaround we made here is to keep the nativePtr value even after the unref() only if the object is still reffed by the SDK,
        so the few methods calls following the finalize() will still work because we know the native pointer still exists.*/

        /*This solution will work as long as the objects still have a ref at C level, which is the case with ChatMessages because they are hold by the Events.
        Should we have this problem again in a situation where the C no longer holds a reference (resulting in the C object being  destroyed by finalize()), 
        another solution is possible (but more complex to implement).
        This solution would consist in implementing at C level, a queue of belle_sip_object_t that are loosing their last ref. 
        Instead of being deleted immediately by belle_sip_object_unref() calling belle_sip_object_delete(), 
        unref() would place the object onto this "C finalizer queue", the delete() call would then be performed later, 
        by a hook added into the main android looper.
        This solution should work because the actual destruction will be performed from the looper context, 
        so in a place where the local java object causing our troubles no longer exists for sure.
        This solution has however the inconvenience that it can work within an application that uses a single thread to call liblinphone API.*/
        
		if (nativePtr != 0) {
			boolean destroyed = unref(nativePtr);
            if (destroyed) {
			    nativePtr = 0;
            }
		}
		super.finalize();
	}
{{/isNotLinphoneFactory}}

    @Override
    public void setUserData(Object data) {
        userData = data;
    }

    @Override
    public Object getUserData() {
        return userData;
    }
}
